`timescale 1ns / 1ns
`include "../include/cpu_defines.sv"
/*
    启动PMON和linux�???要的
    异常支持
    0x0     Int     Interrupt
    0x1     Mod     TLB modification exception
    0x2     TLBL    TLB exception (load or instruction fetch)
    0x3     TLBS    TLB exception (store)
    0x4     AdEL    Address error exception (load or instruction fetch)
    0x5     AdES    Address error exception (store)
    0x8     Sys     Syscall exception
    0x9     Bp      Breakpoint exception
    0xa     RI      Reserved instruction exception
    0xb     CpU     Coprocessor Unusable exception
    0xc     Ov      Arithmetic Overflow exception
    0xd     Tr      Trap exception
    
    cp0寄存�???
    Index Register      (CP Register 0, Select 0)
    Random Register     (CPO Register 1, Select 0)
    EntryLo0, EntryLol  (CPO Registers 2 and 3, Select 0)
    Context Register    (CP Register 4, Select 0)
    PageMask Register   (CPO Register 5, Select 0)
    Wired Register      (CP Register 6. Select 0)
    Bad VAddr Register  (CP Register 8, Select 0)
    Count Register      (CPO Register 9. Select 0)
    Entrylli Register   (CO Register 10, Select 0)
    Compare Register    (CO Register 11, Select 0)
    Status Register     (CP Register 12, Select 0)
    Cause Register      (CP0 Register 13. Select 0)
    Exception Program Counter (CP Register 14, Select 0)
    Processor Identification (CP Register 15, Select 0)
    EBase Register      (CPO Register 15, Select 1)
    Configuration Register (CO Register 16, Select 0)
    Configuration Register 1 (CP Register 16, Select 1)
    TagLo Register      (CP Register 28, Select 0, 2)
    TagHi Register      (CPO Register 29, Select 0, 2)
*/
//EntryHi
`define VPN2_BITS 31:13
`define ASID_BITS 7:0
//G bit in TLB entry
`define G_BIT 12
//PageMask
`define MASK_BITS 28:13
//EntryLo
`define PFN_BITS 25:6
`define FLAG_BITS 5:0
`define V_BIT 1
`define D_BIT 2
`define C_BITS 5:3

// 中断的设计思路:
// 因为在乱序处理器总哦贵很难获得下一条指令的地址
// 因此可以等待指令退休，然后清空这条指令的执行结果

module CP0(
    input logic cpu_clk,
    input logic cpu_rst_n,
	input logic clear,
	input logic retire_en,
	input logic normal_retire_en,
	(* mark_debug="true" *)
    input logic [`EXC_CODE_BUS] retire_exccode,
    input logic [31: 0] wdata1,
    input logic [5: 0] waddr1,
    output logic [31: 0] rdata1,
    input logic [5: 0] raddr1,
    input logic we1,
    input logic [`CP0OP_BUS] cp0_op,

    input logic delay,
	input logic int_ds,
	input logic hard_int_ds,
	(* mark_debug="true" *)
    input logic [`WORD_BUS] pc,
	input logic [31: 0] int_pc,
	input logic [31: 0] hard_int_pc,
    input logic [5:0] int_i, 
	input logic vaddr_we,
	input logic entryhi_we,
    input logic [31:0] vaddr,
	input logic tlb_we,
	input logic [4: 0] tlb_type,
	// input logic weak_vaddr_we, // id阶段产生，一定是流水线最前端
	// input logic [31: 0] weak_vaddr,
    // input logic out_rob_en,
	// output logic [2: 0] soft_en, // soft_en[2]: status[0]
	output logic user_mode,
	output logic Caseint,
    output logic exception_flush,
    output logic [`WORD_BUS] excaddr,
`ifdef CPU_DEBUG
	output logic [31: 0] debug_cp0_random,
	output logic [31: 0] debug_cp0_cause,
	output logic [31: 0] debug_cp0_count,
	input logic cp0_issue_queue_end,
`endif
    //tlb 相关指令
    input wire [31:0] entry_lo0_in,
    input wire [31:0] entry_lo1_in,
    input wire [31:0] page_mask_in,
    input wire [31:0] entry_hi_in,
    input wire [31:0] index_in,
    output wire [31:0] entry_hi_out, 
    output wire [31:0] page_mask_out,
    output wire [31:0] entry_lo0_out,
    output wire [31:0] entry_lo1_out,
    output wire [31:0] index_out,
    output wire [31:0] random_out,
	output logic [31: 0] taglo_out,
	output logic kseg0_uncache
    );
    logic [31:0] badvaddr;
	(*mark_debug = "true"*)
    logic [31:0] status;
	(*mark_debug = "true"*)
    logic [31:0] cause;
	(*mark_debug = "true"*)
    logic [31:0] epc;
    logic [31:0] Count;
    logic [31:0] Compare;
    logic [31:0] entryhi;
    logic [31:0] entrylo0;
	logic [31: 0] entrylo1;
    logic [31:0] index;
	logic [31: 0] random;
    logic [31: 0] pagemask;
	logic [31: 0] taghi;
	logic [31: 0] taglo;
	logic [31: 0] _config;
	logic [31: 0] errorepc;
	logic [31: 0] wired;
	logic [31: 0] _context;
	logic [31: 0] config1;
	logic [31: 0] prid;
	logic [31: 0] ebase;

	// 优化时序
	logic [31: 0] entryhi_tlb;
	logic [31: 0] pagemask_tlb;
	logic [31: 0] entrylo0_tlb;
	logic [31: 0] entrylo1_tlb;
	logic [31: 0] index_tlb;
	logic [31: 0] random_tlb;
	logic [31: 0] taglo_cache;
	logic [31: 0] entrylo0_in_next;
	logic [31: 0] entrylo1_in_next;
	logic [31: 0] entryhi_in_next;
	logic [31: 0] index_in_next;


	logic Count_interrupt;
	// logic int_interrupt;
	logic [`EXC_CODE_BUS] exccode, input_exccode;
	// logic [31: 0] random_gen;
	logic tlbwr, tlbwi, tlbr, tlbp, tlbp_next, tlbr_next, mtc0_index, mtc0_entry_hi, mtc0_entry_lo0, mtc0_entry_lo1, mtc0_page_mask;
	logic status_o, Status_ExL, Status_IE, Status_IM, Status_SW, Cause_IP;

    assign tlbwr = tlb_we && tlb_type[3];
    assign tlbwi = tlb_we && tlb_type[2];
    assign tlbr  = tlb_we && tlb_type[1];
    assign tlbp  = tlb_we && tlb_type[0];
    assign mtc0_index     = we1 & (waddr1 == `Index);
    assign mtc0_entry_hi  = we1 & (waddr1 == `EntryHi);
    assign mtc0_entry_lo0 = we1 & (waddr1 == `EntryLo0);
    assign mtc0_entry_lo1 = we1 & (waddr1 == `EntryLo1);
    assign mtc0_page_mask = we1 & (waddr1 == `PageMask);
	assign kseg0_uncache = _config[2: 0] != 2'b11;

    assign entry_hi_out = entryhi_tlb;
    assign entry_lo0_out = entrylo0_tlb;
    assign entry_lo1_out = entrylo1_tlb;
	assign taglo_out = taglo_cache;
	assign index_out = index_tlb;
    assign random_out = random_tlb;
    assign page_mask_out = pagemask_tlb;

`ifdef CPU_DEBUG
	// assign debug_cp0_cause = cause;
	logic [5: 0] before_raddr1;
	always_ff @(posedge cpu_clk)begin
		before_raddr1 <= raddr1;
		debug_cp0_cause <= cause;
		debug_cp0_random <= random;
		if(cpu_rst_n == 1'b0)begin
			debug_cp0_count <= 0;
		end
		else begin
			if(cp0_issue_queue_end && raddr1 == `Count)begin
				debug_cp0_count <= Count;
			end
		end
	end
`endif
    always_ff @(posedge cpu_clk) begin
		tlbp_next <= tlbp;
		tlbr_next <= tlbr;
		entrylo0_in_next <= entry_lo0_in;
		entrylo1_in_next <= entry_lo1_in;
		entryhi_in_next <= entry_hi_in;
		index_in_next <= index_in;
		if(cpu_rst_n == 1'b0) begin
			index <= 0;
			entryhi <= 0;
			entrylo0 <= 0;
			entrylo1 <= 0;
            pagemask <= 0;
			random <= 31;
			index_tlb <= 0;
			entryhi_tlb <= 0;
			entrylo0_tlb <= 0;
			entrylo1_tlb <= 0;
            pagemask_tlb <= 0;
			random_tlb <= 31;
		end
		else begin
			random[4: 0] <= we1 && waddr1 == `Wired || random[4: 0] == wired[4: 0] ? 5'd31 : random[4: 0] - 1;
			index[31] <= tlbp_next ? index_in_next[31] : index[31];
			index[`TLB_WIDTH] <=    tlbp_next ? index_in_next[`TLB_WIDTH] : 
									mtc0_index ? wdata1[`TLB_WIDTH] : index[`TLB_WIDTH];

			entrylo0[`PFN_BITS]  <= tlbr_next ? entrylo0_in_next[`PFN_BITS]:
									mtc0_entry_lo0 ? wdata1[`PFN_BITS] : entrylo0[`PFN_BITS];
			entrylo0[`FLAG_BITS] <= tlbr_next ? entrylo0_in_next[`FLAG_BITS] :
									mtc0_entry_lo0 ? wdata1[`FLAG_BITS] : entrylo0[`FLAG_BITS];

			entrylo1[`PFN_BITS]  <= tlbr_next ? entrylo1_in_next[`PFN_BITS]:
									mtc0_entry_lo1 ? wdata1[`PFN_BITS] : entrylo1[`PFN_BITS];
			entrylo1[`FLAG_BITS] <= tlbr_next ? entrylo1_in_next[`FLAG_BITS] :
									mtc0_entry_lo1 ? wdata1[`FLAG_BITS] : entrylo1[`FLAG_BITS];
            entryhi[31: 13]     <=  entryhi_we ? vaddr[31: 13] : 
                                    tlbr_next ? entryhi_in_next[31: 13] : 
                                    mtc0_entry_hi ? wdata1[31: 13] : entryhi[31:13];
            entryhi[12: 0]      <=  tlbr_next ? entryhi_in_next[12: 0]  :
                                    mtc0_entry_hi ? wdata1[12: 0] : entryhi[12: 0];

			random_tlb[4: 0] <= we1 && waddr1 == `Wired || random_tlb[4: 0] == wired[4: 0] ? 5'd31 : random_tlb[4: 0] - 1;
			index_tlb[31] <= tlbp_next ? index_in_next[31] : index_tlb[31];
			index_tlb[`TLB_WIDTH] <=    tlbp_next ? index_in_next[`TLB_WIDTH] : 
									mtc0_index ? wdata1[`TLB_WIDTH] : index_tlb[`TLB_WIDTH];

			entrylo0_tlb[`PFN_BITS]  <= tlbr_next ? entrylo0_in_next[`PFN_BITS]:
									mtc0_entry_lo0 ? wdata1[`PFN_BITS] : entrylo0_tlb[`PFN_BITS];
			entrylo0_tlb[`FLAG_BITS] <= tlbr_next ? entrylo0_in_next[`FLAG_BITS] :
									mtc0_entry_lo0 ? wdata1[`FLAG_BITS] : entrylo0_tlb[`FLAG_BITS];

			entrylo1_tlb[`PFN_BITS]  <= tlbr_next ? entrylo1_in_next[`PFN_BITS]:
									mtc0_entry_lo1 ? wdata1[`PFN_BITS] : entrylo1_tlb[`PFN_BITS];
			entrylo1_tlb[`FLAG_BITS] <= tlbr_next ? entrylo1_in_next[`FLAG_BITS] :
									mtc0_entry_lo1 ? wdata1[`FLAG_BITS] : entrylo1_tlb[`FLAG_BITS];
            entryhi_tlb[31: 13]     <=  entryhi_we ? vaddr[31: 13] : 
                                    tlbr_next ? entryhi_in_next[31: 13] : 
                                    mtc0_entry_hi ? wdata1[31: 13] : entryhi_tlb[31:13];
            entryhi_tlb[12: 0]      <=  tlbr_next ? entryhi_in_next[12: 0]  :
                                    mtc0_entry_hi ? wdata1[12: 0] : entryhi_tlb[12: 0];
		end
    end

    assign status_o = status;
    // assign cause_o = cause;
    //hard int  
    assign Status_SW = status[15:10];
    // assign Cause_SW = cause[15:10];
    assign Status_IE = status[0];
    assign Status_ExL = status[1];
	//assign int_interrupt = status[1]== 0 && status[0] == 1 && ((status[15:10] & cause[15:10]) !=8'h00);
    //count int 
	// always_ff @(posedge cpu_clk)begin
	// 	Count_interrupt <=  we1 && waddr1 == `Compare || cpu_rst_n == 1'b0 ? 1'b0 : (Count == Compare && Compare != 0) ? 1'b1 : Count_interrupt;
	// end
	always_ff @(posedge cpu_clk)begin
		Count_interrupt <= (Count == Compare && Compare != 0);
	end
    
    //soft int 
    assign Status_IM = status[9:8];
    // assign Casue_IP = cause[9:8];

    //signal
    logic [5:0] ext_interrupt_signal;
    // always @(posedge cpu_clk) begin
    //     if(cpu_rst_n == 1'b0) begin
    //         ext_interrupt_signal <= 0;
    //     end
    //     else begin
    //         ext_interrupt_signal <= Count_interrupt? {Count_interrupt,int_i[4:0] }:{int_i[5:0]};
    //     end
    // end
    // signal + interrupt
	assign ext_interrupt_signal = cause[15: 10];
    wire [7:0] int_signal  = {ext_interrupt_signal,cause[9:8]};
    wire [7:0] int_mark    = {status[15:10],status[9:8]};
    wire [7:0] int_ready = int_signal & int_mark;

    logic divClk;

	// always_ff @(posedge cpu_clk)begin
	// 	Caseint <= (|int_ready)  && Status_IE && !Status_ExL && normal_retire_en;
	// end
	assign user_mode = ~(status[1] | status[2] | (~status[3] & ~status[4]));
    assign Caseint = (|int_ready)  && Status_IE && !Status_ExL && normal_retire_en;
    assign exccode = Caseint ? `EXC_INT :retire_exccode;
	assign input_exccode = exccode == `EXC_ITLBL || exccode == `EXC_RTLBL ? `EXC_TLBL :
							exccode == `EXC_ITLBS || exccode == `EXC_RTLBS ? `EXC_TLBS : exccode;

	// RanGen32 ran_gen(cpu_rst_n, 32'h1d74be93, cpu_clk, random_gen);
    always_ff @(posedge cpu_clk) begin
        if(cpu_rst_n == 1'b0) begin
            divClk        <= 0;
            status[31:28] <= 4'b0000;
            status[27:23] <= 5'h0;
            status[26   ] <= 1'b0;
            status[23   ] <= 1'b0;
            status[22   ] <= 1'b1;
            status[21   ] <= 1'b0;
            status[20:16] <= 5'b0;
            status[15:8 ] <= 8'b0;
            status[7:4  ] <= 4'b0;
            status[3    ] <= 1'b0;
            status[2    ] <= 1'b0;
            status[1    ] <= 1'b0;
            status[0    ] <= 1'b0;

            cause [31:0 ] <= 32'b0;

			badvaddr      <= 0;
			epc           <= 0;
            Count         <= 0;
            Compare       <= 0;
			taghi <= 0;
			taglo <= 0;
			taglo_cache <= 0;
			_config[31] <= 1'b1;
			_config[30: 16] <= 0;
			_config[15] <= 0;
			_config[14: 13] <= 0;
			_config[12: 10] <= 0;
			_config[9: 7] <= 1;
			_config[6: 4] <= 0;
			_config[3] <= 1'b0;
			_config[2: 0] <= 2'b11;
			wired <= 0;
			_context <= 0;
			config1[31] <= 0;
			config1[30: 25] <= 31;
			config1[24 :22] <= 1;
			config1[21: 19] <= 4;
			config1[18: 16] <= 3;
			config1[15: 13] <= 1;
			config1[12: 10] <= 4;
			config1[9: 7] <= 3;
			config1[6]<= 0;
			config1[5] <= 0;
			config1[4] <= 0;
			config1[3] <= 0;
			config1[2] <= 0;
			config1[1] <= 0;
			config1[0] <= 0;

			prid <= 32'h00018000;

			ebase[31: 30] <= 2'b10;
			ebase[29: 0] <= 0;
        end
		else begin
			// if(retire_exccode == `EXC_ERET && epc[1: 0] != 2'b00)begin
			// 	badvaddr <= epc;
			// end
            divClk     <= ~divClk;
			if(!(we1 && waddr1 == `Count))begin
				Count      <= Count + divClk;
			end
			// 在mips release2 中才需要
			// 	cause[30]  <= Count_interrupt;

			// cause[15]只由定时器中断控制
			cause[15:10] <= (int_i | {Count_interrupt | cause[15], 5'b0}) & {!(we1 && waddr1 == `Compare), 5'b11111} ;
	
			if(vaddr_we)begin
				badvaddr <= vaddr;
			end
			if(entryhi_we)begin
				_context[22: 4] <= vaddr[31: 13];
			end
			if(retire_en && exccode == `EXC_CpU)begin
				cause[29: 28] <= 1'b1;
			end
			if(we1) begin
                case(waddr1)
                `Status: begin
					status <= wdata1 & 32'hfff8ff17;
                end
                `Cause:begin
					cause[27] <= wdata1[27];
					cause[23: 22] <= wdata1[23: 22];
                    cause[9:8] <= wdata1[9:8];
                end
                `EPC:
                    epc <= wdata1;
                `Count:
                    Count <= wdata1;
                `Compare:
                    Compare <= wdata1;
				`Taghi: taghi <= wdata1;
				`Taglo: begin
					taglo <= wdata1;
					taglo_cache <= wdata1;
				end
				`_Config: begin
					_config[30: 25] <= wdata1[30: 25];
				end
				`Wired: wired <= wdata1[4: 0];
				`Context: _context[31: 23] <= wdata1[31: 23];
				`Ebase: ebase[29: 12] <= wdata1[29: 12];
				endcase
			end

			if(retire_en || Caseint )begin
				case(exccode)
				`EXC_NONE:;
				`EXC_ERET: begin
					if(epc[1: 0] != 2'b00 && status[1] == 1'b0)begin
						status[1] <= 1'b1;
						cause[6:2] <= `EXC_AdEL;
						if(delay)begin
							cause[31] <= 1'b1;
						end
					end
					else begin
						do_eret();
					end
				end
				`EXC_FLUSH:begin
				end
				default:
					do_exc();
				endcase
			end
		end

                

    end


    //flush xin hao
	assign exception_flush = retire_en && retire_exccode != `EXC_NONE || Caseint;
    //do excepiton
    task do_exc;begin
        if(status[1] == 1'b0) begin
			if(exccode == `EXC_INT)begin
				cause[31] <= hard_int_ds;
				epc <= hard_int_ds ? hard_int_pc - 4 : hard_int_pc;
				// epc <= int_pc - (3'b100 & {3{int_ds}});
			end
            else if(delay) begin
                cause[31] <= 1;
                epc <= pc -4;
            end
            else begin
                cause[31] <= 0;
                epc <= pc;
            end
        end
        status[1] <= 1'b1;
        cause[6:2] <= input_exccode;
        end
    endtask
    task do_eret;begin
        status[1] <= 0;
    end
    endtask;
	logic [19: 0] exc_hi;
	logic [11: 0] exc_lo, exc_tlblo, exc_normallo, exc_intlo;
	assign exc_hi = status[22] ? 20'hbfc00 : ebase[31: 12];
	always_comb begin
		case({status[22], status[1]})
		2'b00: exc_tlblo = 12'h000;
		2'b01: exc_tlblo = 12'h180;
		2'b10: exc_tlblo = 12'h200;
		2'b11: exc_tlblo = 12'h380;
		endcase
		case({status[22], cause[23]})
		2'b00: exc_intlo = 12'h180;
		2'b01: exc_intlo = 12'h200;
		2'b10: exc_intlo = 12'h380;
		2'b11: exc_intlo = 12'h400;
		endcase
	end
	assign exc_normallo = status[22] ? 12'h380 : 12'h180;
	assign exc_lo = exccode == `EXC_RTLBL || exccode == `EXC_RTLBS ? exc_tlblo : exccode == `EXC_INT ? exc_intlo : exc_normallo;
	assign excaddr = exccode == `EXC_ERET ? epc : exccode == `EXC_FLUSH ? pc + 4 : {exc_hi, exc_lo};

    always_comb begin
        case(raddr1)
        `Status   :     rdata1 = status;
        `Cause    :     rdata1 = cause;
        `EPC      :     rdata1 = epc;
        `BadVaddr :     rdata1 = badvaddr;
        `Count    :     rdata1 =  Count;
        `Compare  :     rdata1 = Compare;
		`Index 	  : 	rdata1 = index;
		`EntryHi  : 	rdata1 = entryhi;
		`EntryLo0 : 	rdata1 = entrylo0;
		`EntryLo1 : 	rdata1 = entrylo1;
		`Random   : 	rdata1 = random;
		`Taghi    : 	rdata1 = taghi;
		`Taglo    : 	rdata1 = taglo;
		`_Config  : 	rdata1 = _config;
		`PageMask : 	rdata1 = pagemask;
		`Wired    :		rdata1 = wired;
		`Context  : 	rdata1 = _context;
		`Config1  : 	rdata1 = config1;
		`PRid 	  : 	rdata1 = prid;
		`Ebase    :     rdata1 = ebase;
        default   :     rdata1 = 32'b0;
        endcase
    end
endmodule
